"""
领域模型和数据库之间的转换。
"""
from __future__ import annotations

from dataclasses import dataclass
from typing import Optional, Tuple

from sqlalchemy.orm import Session

from ..db.parameters import crud as parameter_crud
from ..db import Project as DBProject, ParameterCategory, Parameter, ParameterValue
from .domain import (
    ProjectSnapshot,
    ProjectMetadata,
    CategorySnapshot,
    ParameterSnapshot,
    ParameterValueSnapshot,
)


class ProjectNotFoundError(RuntimeError):
    """指定项目未找到"""


@dataclass
class PersistResult:
    project: DBProject
    created_categories: int = 0
    created_parameters: int = 0
    created_values: int = 0


class ProjectRepository:
    """项目数据的仓储层"""

    def __init__(self, db: Session):
        self.db = db

    # ---------- 读取 ----------
    def load_by_name_en(self, name_en: str) -> ProjectSnapshot:
        project = parameter_crud.get_project_by_name_en(self.db, name_en)
        if not project:
            raise ProjectNotFoundError(f"项目 '{name_en}' 不存在")

        categories = parameter_crud.get_parameter_categories(self.db, project.id)

        snapshot = ProjectSnapshot(
            metadata=ProjectMetadata(
                name=project.name,
                name_en=project.name_en,
                description=project.description,
                time_horizon=project.time_horizon,
                start_year=project.start_year,
                year_step=project.year_step,
                end_year=project.end_year,
                id=project.id,
                created_at=project.created_at,
                updated_at=project.updated_at,
            )
        )

        for category in categories:
            snapshot.categories.append(self._load_category(category))

        return snapshot

    def _load_category(self, category: ParameterCategory) -> CategorySnapshot:
        parameters = parameter_crud.get_parameters_with_values(self.db, category.id)
        category_snapshot = CategorySnapshot(
            name=category.name,
            name_en=category.name_en,
            description=category.description,
            sort_order=category.sort_order or 0,
            id=category.id,
            created_at=category.created_at,
            updated_at=category.updated_at,
        )
        for param in parameters:
            category_snapshot.parameters.append(self._map_parameter_dict(param))
        return category_snapshot

    def _map_parameter_dict(self, param_dict) -> ParameterSnapshot:
        snapshot = ParameterSnapshot(
            name=param_dict["name"],
            name_en=param_dict["name_en"],
            param_type=param_dict["param_type"],
            unit=param_dict.get("unit"),
            description=param_dict.get("description"),
            is_list=param_dict["is_list"],
            is_year_related=param_dict["is_year_related"],
            list_length=param_dict.get("list_length"),
            sort_order=param_dict.get("sort_order", 0),
            id=param_dict.get("id"),
            created_at=param_dict.get("created_at"),
            updated_at=param_dict.get("updated_at"),
        )
        if param_dict["is_list"]:
            values = param_dict.get("values", [])
            for value_entry in sorted(values, key=lambda v: v.get("list_index") or 0):
                snapshot.values.append(
                    ParameterValueSnapshot(
                        index=value_entry.get("list_index"),
                        value=value_entry.get("value"),
                        id=value_entry.get("id"),
                        created_at=value_entry.get("created_at"),
                        updated_at=value_entry.get("updated_at"),
                    )
                )
        else:
            value = param_dict.get("current_value")
            # 对于非列表参数，尝试从 values 中获取第一个值的信息
            values = param_dict.get("values", [])
            value_info = values[0] if values else {}
            snapshot.values.append(
                ParameterValueSnapshot(
                    index=None,
                    value=value if value is not None else None,
                    id=value_info.get("id"),
                    created_at=value_info.get("created_at"),
                    updated_at=value_info.get("updated_at"),
                )
            )
        return snapshot

    # ---------- 持久化 ----------
    def persist_snapshot(
        self,
        snapshot: ProjectSnapshot,
        *,
        ensure_unique: bool = True,
    ) -> PersistResult:
        """
        将快照持久化到数据库。

        ensure_unique=True 时，会遇到同名(英文)项目时自动追加 _copy{n} 后缀。
        """
        project_name, project_name_en = self._resolve_project_name(snapshot, ensure_unique)

        project = DBProject(
            name=project_name,
            name_en=project_name_en,
            description=snapshot.metadata.description,
            time_horizon=snapshot.metadata.time_horizon,
            start_year=snapshot.metadata.start_year,
            year_step=snapshot.metadata.year_step,
            end_year=snapshot.metadata.end_year,
        )
        self.db.add(project)
        self.db.flush()

        categories_count = 0
        parameters_count = 0
        values_count = 0

        for category_snapshot in snapshot.categories:
            category = ParameterCategory(
                project_id=project.id,
                name=category_snapshot.name,
                name_en=category_snapshot.name_en,
                description=category_snapshot.description,
                sort_order=category_snapshot.sort_order,
            )
            self.db.add(category)
            self.db.flush()
            categories_count += 1

            for parameter_snapshot in category_snapshot.parameters:
                parameter = Parameter(
                    category_id=category.id,
                    name=parameter_snapshot.name,
                    name_en=parameter_snapshot.name_en,
                    param_type=parameter_snapshot.param_type,
                    unit=parameter_snapshot.unit,
                    description=parameter_snapshot.description,
                    is_list=parameter_snapshot.is_list,
                    is_year_related=parameter_snapshot.is_year_related,
                    list_length=parameter_snapshot.list_length,
                    sort_order=parameter_snapshot.sort_order,
                )
                self.db.add(parameter)
                self.db.flush()
                parameters_count += 1

                for value_snapshot in parameter_snapshot.values:
                    if value_snapshot.value is None:
                        continue
                    value = ParameterValue(
                        parameter_id=parameter.id,
                        value=value_snapshot.value,
                        list_index=value_snapshot.index,
                    )
                    self.db.add(value)
                    values_count += 1

        self.db.commit()
        self.db.refresh(project)

        return PersistResult(
            project=project,
            created_categories=categories_count,
            created_parameters=parameters_count,
            created_values=values_count,
        )

    def _resolve_project_name(
        self,
        snapshot: ProjectSnapshot,
        ensure_unique: bool,
    ) -> Tuple[str, str]:
        base_name = snapshot.metadata.name
        base_name_en = snapshot.metadata.name_en
        if not ensure_unique:
            return base_name, base_name_en

        suffix = 0
        name = base_name
        name_en = base_name_en
        while (
            self.db.query(DBProject).filter(DBProject.name_en == name_en).first()
            is not None
        ):
            suffix += 1
            name = f"{base_name} 副本{suffix}"
            name_en = f"{base_name_en}_copy{suffix}"
        return name, name_en

